%%%%%%%%%%%%%%%%%%%%
% CSS Compressor
% Usage: compress_css('1.css','1c.css').
%%%%%%%%%%%%%%%%%%%%
compress_css(FileIn,FileOut) :-
	css_lexer_from_file(FileIn,List),
	strip_comments(List,List0),
	(
		(css_parser(List1,List0,[]),write(List1),write_css_to_file(FileOut,List1));
		(write('CSS file syntax error.\n'))
	).


%%%%%%%%%%%%%%%%%%%%
% CSS Lexer
% Split CSS into words and delimiters
%%%%%%%%%%%%%%%%%%%%
css_lexer_from_file(FileIn,List) :-
	see(FileIn),
	read_css(CharList),
	close(FileIn),
	css_lexer(CharList,List).

read_css(CharList) :- read_css(CharList,Hole-Hole).
read_css(CharList,OpenList-Hole) :-
	get_code(Ch),
	(
		(Ch<0,CharList-[]=OpenList-Hole);	% end of file
		(diff_append(OpenList-Hole,[Ch|Hole1]-Hole1,NewOpenList-NewHole),read_css(CharList,NewOpenList-NewHole))
	).

css_lexer(CharList,Ans) :- css_lexer(CharList,Ans,Hole-Hole,[],0).
css_lexer([],Ans,OpenList-Hole,TempRevList,_) :-
	reverse(TempRevList,Cell),add_word_to_list(Cell,OpenList-Hole,Ans-[]).
css_lexer([Ch|CharList],Ans,OpenList-Hole,TempRevList,BracketDepth) :-
	(
		Ch=0'( -> NewBracketDepth is BracketDepth+1;
		Ch=0') -> NewBracketDepth is BracketDepth-1;
		NewBracketDepth=BracketDepth
	),
	NewTempList=[Ch|TempRevList],
	(
		(
			NewBracketDepth=<0,	% never split in a bracket
			find_delim(Word,Delim,NewTempList,[]),
			add_word_to_list(Word,OpenList-Hole,NewOpenList0-NewHole0),
			add_word_to_list(Delim,NewOpenList0-NewHole0,NewOpenList-NewHole),
			css_lexer(CharList,Ans,NewOpenList-NewHole,[],NewBracketDepth)
		);
		css_lexer(CharList,Ans,OpenList-Hole,NewTempList,NewBracketDepth)
	).

add_word_to_list(Word,OpenList-Hole,NewOpenList-NewHole) :-
	(Word=[];([H|_]=Word,is_space(H))) -> (OpenList-Hole=NewOpenList-NewHole);	% for empty of space word
	diff_append(OpenList-Hole,[Word|Hole1]-Hole1,NewOpenList-NewHole).		% for other word


% find a delimiter from a reversed list
% if found, return word and dilimiter (non-reversed)
find_delim(Word,Delim) -->
	rev_delim(Delim),
	rev_word(Word,[]).

rev_word(Word,Temp) --> [],{Word=Temp}.
rev_word(Word,Temp) --> [Ch], rev_word(Word,[Ch|Temp]).

rev_delim(D) --> [C],{D=[C],is_delim(D)}.
rev_delim(D) --> [C1,C2],{D=[C2,C1],is_delim(D)}.


%%%%%%%%%%%%%%%%%%%%
% Check if a string is a delimiter
%%%%%%%%%%%%%%%%%%%%
delims(Delims) :- Delims=[" ","\t","\r","\n","{","}",";",":","*/","/*","!","@"].
is_delim(List) :- delims(Delims),is_delim(List,Delims),!.
is_delim(_,[]):- fail.
is_delim(List,[H|Delims1]) :-
	List=H;
	is_delim(List,Delims1).
not_delim(W) :- (is_delim(W),!,fail);true.


%%%%%%%%%%%%%%%%%%%%
% Strip comments from L1 and save the rest in L2
% Eg. L1=["aa","/*","bb","cc","*/","dd"], L2=["aa","dd"]
%%%%%%%%%%%%%%%%%%%%
strip_comments(L1,L2) :-
	strip_comments(L1,L2,out,H-H).
strip_comments([],L2,_,OpenList-Hole) :- L2-[]=OpenList-Hole.
strip_comments([Word|L1],L2,LastStatus,OpenList-Hole) :-
	(
		(Word="/*",ThisStatus=in,PNextStatus=in);
		(Word="*/",ThisStatus=in,PNextStatus=out);
		(ThisStatus=LastStatus,PNextStatus=LastStatus)
	),
	(
		(ThisStatus=in,strip_comment2(L1,L2,PNextStatus,OpenList-Hole));
		(diff_append(OpenList-Hole,[Word|Hole1]-Hole1,NewOpenList-NewHole),strip_comments(L1,L2,PNextStatus,NewOpenList-NewHole))
	).


%%%%%%%%%%%%%%%%%%%%
% Common Utilities
%%%%%%%%%%%%%%%%%%%%
diff_append(OpenList1-Hole1,Hole1-Hole2,OpenList1-Hole2).
reverse(L1,L2) :- reverse(L1,L2,[]).
reverse([],Z,Z).
reverse([H|T],L,L0) :- reverse(T,L,[H|L0]).

is_space(Ch) :-
	Ch = 0' ;
	Ch = 0'\t;
	Ch = 0'\n;
	Ch = 0'\r.
is_alphabet(Ch) :-
	Ch >= 0'a, Ch =< 0'z;
	Ch >= 0'A, Ch =< 0'Z.
is_digit(Ch) :-
	Ch >= 0'0, Ch =< 0'9.
is_eof(Ch) :- Ch < 0.


%%%%%%%%%%%%%%%%%%%%
% Write css to file 
%%%%%%%%%%%%%%%%%%%%
write_css_to_file(File,List) :-
	tell(File),
	write_css_file(List),
	told,!.

write_css_file([]).
write_css_file(CssBody) :-
	write_css_body(CssBody).

write_css_body([]).
write_css_body([Type,Data]) :-
	Type=css->write_css(Data);
	Type=at_rule->write_at_rule(Data);
	Type=at_css->write_at_css(Data).
write_css_body([[Type,Data]|List]) :-
	(Type=css->write_css(Data);
	Type=at_rule->write_at_rule(Data);
	Type=at_css->write_at_css(Data)),
	write('\n'),
	write_css_body(List).

write_at_rule([Name,Value]) :-
	write('@'),
	write(Name),
	write(' '),
	write(Value),
	write(';').
write_at_css(Data) :-
	write('@'),
	write_css(Data).

write_css([Selectors,Properties]) :-
	write_selectors(Selectors),
	write('{'),
	write_properties(Properties),
	write('}').

write_selectors([Selector]) :- write_selector(Selector).
write_selectors([Selector|Selectors]) :-
	write_selector(Selector),
	write(','),
	write_selectors(Selectors).
write_selector([Field]) :- write_field(Field).
write_selector([Field|Selector]) :-
	write_field(Field),
	write(' '),
	write_selector(Selector).
write_field([X,Y]) :-
	write(X),
	(Y=[];(write(':'),write(Y))).

write_properties([Property]) :- write_property(Property).
write_properties([Property|Properties]) :-
	write_property(Property),
	write(';'),
	write_properties(Properties).

write_property([Name,Values,Imp]) :-
	write_name(Name),
	write(':'),
	write_values(Values),
	(Imp=[];write(' !important')).

write_name([H]) :- write_name_part(H).
write_name([H|Names]) :- 
	write_name_part(H),
	write(':'),
	write_name(Names).

write_name_part(H) :- write(H).

write_values([Value]) :- write_value(Value).
write_values([Value|Values]) :-
	write_value(Value),
	write(' '),
	write_values(Values).
write_value(Value) :-
	(Value='0px',write('0'));
	write(Value).


%%%%%%%%%%%%%%%%%%%%
% DCG for CSS (ver 2.0)
% Return X is a list of structured CSS with compression
%%%%%%%%%%%%%%%%%%%%
css_parser(X) --> css_body(X0),{sort(X0,X)}.

css_body([]) --> [].
css_body([X|Y]) -->
	css_item(X),
	css_body(Y).

css_item([at_rule,[X,Y]]) --> at_rule([X,Y]).
css_item([at_css,[X,Y]]) --> at_css([X,Y]).
css_item([css,[X,Y]]) --> css([X,Y]).

% for "at-rules"
at_css([X,Y]) --> ["@"],css([X,Y]).	% e.g. @page thin:first { size: 3in 8in } 
at_rule([X,Y]) -->			% e.g. @import url("foo.css") screen, print;
	["@"],
	rule_name(X),
	rule_value(Y),
	rule_media,
	[";"].
rule_name(A) -->
	[W],{not_delim(W),atom_codes(A,W)}.
rule_value(A) -->
	[W],{not_delim(W),atom_codes(A,W)}.
rule_media --> [].
rule_media -->
	one_media,
	rule_media.
one_media --> [W],{not_delim(W)}.

% for normal css
% e.g. a:active { font-size: 14pt; text-decoration: underline; color: blue; } 
css([X,Y]) -->				
	selector_group(X0),{sort(X0,X)},
	["{"],
	properties(Y0),{sort(Y0,Y)},
	["}"].

selector_group([X]) -->
	selector(X).
selector_group([X|Y]) -->
	selector(X),
	[","],
	selector_group(Y).

selector([X]) -->
	sub_selector(X).
selector([X|Y]) -->
	sub_selector(X),
	selector(Y).

sub_selector([A,[]]) --> element(A).
sub_selector([X,Y]) -->
	element(X),
	[":"],
	pesudo(Y).

element(A) -->
	[W],{not_delim(W),atom_codes(A,W)}.

pesudo(A) -->
	[W],{not_delim(W),atom_codes(A,W)}.

properties([]) --> [].
properties([X]) --> property(X).
properties([X|Y])-->
	property(X),
	[";"],
	properties(Y).

property([X,Y,[]]) -->
	property_name(X),
	[":"],
	property_values(Y).
property([X,Y,Z]) -->
	property_name(X),
	[":"],
	property_values(Y),
	["!"],
	declaration(Z).
declaration(A) -->
	[W],{not_delim(W),((W="important",A='important');A=[])}.

% for filter property
% eg. filter:progid:DXImageTransform.Microsoft.BasicImage(invert=1,GrayScale=1);
property_name([A]) --> [W],{not_delim(W),atom_codes(A,W)}.
property_name([H|T]) -->
	property_name_part(H),
	[":"],
	property_name(T).
	
property_name_part(A) --> [W],{not_delim(W),atom_codes(A,W)}.

property_values([X]) --> property_value(X).
property_values([X|Y]) -->
	property_value(X),
	property_values(Y).

property_value(A) --> [W],{not_delim(W),compress(W,W2),atom_codes(A,W2)}.


% Compress color value like #000000 to #000
compress([0'#,Hex1,Hex1,Hex2,Hex2,Hex3,Hex3],W2) :- W2=[0'#,Hex1,Hex2,Hex3],!.
compress(W1,W2) :- W2=W1.